package logger

import (
	"fmt"
	"github.com/fatih/color"
	"io"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"sync"
)

type logLevel int

func init() {
	initLogger()
}

// log levels
const (
	DEBUG logLevel = iota
	INFO
	WARNING
	ERROR
	FATAL
)

const (
	defaultFlags       = log.LstdFlags
	defaultCallerDepth = 3
)

var (
	defaultLogger       *Logger
	logPrefix           = ""
	levelFlags          = []string{"[DEBUG]", "[INFO]", "[WARN]", "[ERROR]", "[FATAL]"}
	levelFlagTextColors = []color.Attribute{color.FgHiBlue, color.FgHiGreen, color.FgHiYellow, color.FgHiRed, color.FgHiMagenta}
)

type Logger struct {
	*log.Logger
	mu sync.Mutex
}

func newLogger(configs ...WriterConfig) *Logger {
	logger := &Logger{
		mu: sync.Mutex{},
	}

	writers := []io.Writer{os.Stdout}
	for _, config := range configs {
		writers = append(writers, config.Build())
	}
	mw := io.MultiWriter(writers...)
	logger.Logger = log.New(mw, logPrefix, defaultFlags)
	return logger
}

func initLogger(configs ...WriterConfig) {
	defaultLogger = newLogger(configs...)
}

func (logger *Logger) setPrefix(level logLevel) {
	_, file, line, ok := runtime.Caller(defaultCallerDepth)
	levelFlag := color.New(color.Bold, levelFlagTextColors[level]).Sprint(levelFlags[level])
	if ok {
		basePath := filepath.Join(os.Getenv("GOPATH"), "src")
		relPath, _ := filepath.Rel(basePath, file)
		logPrefix = fmt.Sprintf("%-18s [%s:%d] ", levelFlag, relPath, line)
	} else {
		logPrefix = fmt.Sprintf("%-18s ", levelFlag)
	}

	logger.SetPrefix(logPrefix)
}

// Debug prints debug log
func (logger *Logger) debug(v ...interface{}) {
	logger.mu.Lock()
	defer logger.mu.Unlock()
	logger.setPrefix(DEBUG)
	logger.Println(v...)
}

// Info prints normal log
func (logger *Logger) info(v ...interface{}) {
	logger.mu.Lock()
	defer logger.mu.Unlock()
	logger.setPrefix(INFO)
	logger.Println(v...)
}

// Warn prints warning log
func (logger *Logger) warn(v ...interface{}) {
	logger.mu.Lock()
	defer logger.mu.Unlock()
	logger.setPrefix(WARNING)
	logger.Println(v...)
}

// Error prints error log
func (logger *Logger) error(v ...interface{}) {
	logger.mu.Lock()
	defer logger.mu.Unlock()
	logger.setPrefix(ERROR)
	logger.Println(v...)
}

// Fatal prints error log then stop the program
func (logger *Logger) fatal(v ...interface{}) {
	logger.mu.Lock()
	defer logger.mu.Unlock()
	logger.setPrefix(FATAL)
	logger.Fatalln(v...)
}

// Debug prints debug log
func Debug(v ...interface{}) {
	defaultLogger.debug(v...)
}

// Debugf prints debug log with format
func Debugf(format string, v ...interface{}) {
	msg := fmt.Sprintf(format, v...)
	defaultLogger.debug(msg)
}

// Info prints normal log
func Info(v ...interface{}) {
	defaultLogger.info(v...)
}

// Infof prints normal log with format
func Infof(format string, v ...interface{}) {
	msg := fmt.Sprintf(format, v...)
	defaultLogger.info(msg)
}

// Warn prints warning log
func Warn(v ...interface{}) {
	defaultLogger.warn(v...)
}

// Warnf prints warning log with format
func Warnf(format string, v ...interface{}) {
	msg := fmt.Sprintf(format, v...)
	defaultLogger.warn(msg)
}

// Error prints error log
func Error(v ...interface{}) {
	defaultLogger.error(v...)
}

// Errorf prints error log with format
func Errorf(format string, v ...interface{}) {
	msg := fmt.Sprintf(format, v...)
	defaultLogger.error(msg)
}

// Fatal prints error log and then stop the program
func Fatal(v ...interface{}) {
	defaultLogger.fatal(v...)
}

// Fatalf prints error log with format and then stop the program
func Fatalf(format string, v ...interface{}) {
	msg := fmt.Sprintf(format, v...)
	defaultLogger.fatal(msg)
}
